﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;

namespace UnitFundProfitability.BusinessLogic
{
	public class Manager
	{
		#region Helper types
		private class ProfitabilityCalculatingHelper
		{
			private DateTime _startDate;
			private DateTime _endDate;
			private Dictionary<Guid, FundProfitability> _fundsProfitabilities;
			private Dictionary<Guid, FundSnapshot> _startFundsSnapshots;
			private Dictionary<Guid, FundSnapshot> _endFundsSnapshots;
			internal DateTime StartDate
			{
				get
				{
					return _startDate;
				}
				set
				{
					_startDate = value;
				}
			}
			internal DateTime EndDate
			{
				get
				{
					return _endDate;
				}
				set
				{
					_endDate = value;
				}
			}
			internal Dictionary<Guid, FundProfitability> FundsProfitabilites
			{
				get
				{
					return _fundsProfitabilities;
				}
				set
				{
					_fundsProfitabilities = value;
				}
			}
			internal Dictionary<Guid, FundSnapshot> StartFundsSnapshots
			{
				get
				{
					return _startFundsSnapshots;
				}
				set
				{
					_startFundsSnapshots = value;
				}
			}
			internal Dictionary<Guid, FundSnapshot> EndFundsSnapshots
			{
				get
				{
					return _endFundsSnapshots;
				}
				set
				{
					_endFundsSnapshots = value;
				}
			}
		}

		private class FundSnapshot
		{
			private List<Income> _incomes;
			private decimal _unitPrice;
			private decimal _markdownRate;
			internal FundSnapshot()
			{
				_incomes = new List<Income>();
			}
			internal decimal MoneyIncome
			{
				get
				{
					decimal result = 0;
					if (_incomes.Count > 0)
					{
						result =
							(from income in _incomes
							 select income.MoneyAmount - (income.MoneyAmount * ((income.UnitsExpenditureAmount ?? 0) / income.UnitsAmount)))
							.Aggregate((a, b) => a + b);
					}
					return result;
				}
			}
			internal decimal UnitsCount
			{
				get
				{
					decimal result = 0;
					if (_incomes.Count > 0)
					{
						result =
							(from income in _incomes
							 select income.UnitsAmount - (income.UnitsExpenditureAmount ?? 0))
							 .Aggregate((a, b) => a + b);
					}
					return result;
				}
			}
			internal decimal UnitPrice
			{
				get
				{
					return _unitPrice;
				}
				set
				{
					_unitPrice = value;
				}
			}
			internal decimal MarkdownRate
			{
				get
				{
					return _markdownRate;
				}
				set
				{
					_markdownRate = value;
				}
			}
			internal decimal PossibleMoneyExpenditure
			{
				get
				{
					decimal result = 0;
					if (UnitsCount != 0)
					{
						result = CalculatePossibleExpenditure(UnitPrice, UnitsCount, MarkdownRate, MoneyIncome / UnitsCount);
					}
					return result;
				}
			}
			internal void UpdateWithIncomeOperation(Income income)
			{
				if (income == null)
				{
					throw new ArgumentNullException("income");
				}
				Income initialIncome = new Income();
				initialIncome.MoneyAmount = income.MoneyAmount;
				initialIncome.UnitsAmount = income.UnitsAmount;
				initialIncome.UnitsExpenditureAmount = 0;
				_incomes.Add(initialIncome);
			}
			internal void UpdateWithExpenditureOperation(Expenditure expenditure)
			{
				if (expenditure == null)
				{
					throw new ArgumentNullException("expenditure");
				}
				decimal unitsAmountToWriteOff = expenditure.UnitsAmount;
				while (unitsAmountToWriteOff != 0)
				{
					Income incomeToWriteOff = _incomes[0];
					decimal incomeUnitsRemainder = incomeToWriteOff.UnitsAmount - (incomeToWriteOff.UnitsExpenditureAmount ?? 0);
					if (unitsAmountToWriteOff >= incomeUnitsRemainder)
					{
						unitsAmountToWriteOff -= incomeUnitsRemainder;
						_incomes.RemoveAt(0);
					}
					else
					{
						incomeToWriteOff.UnitsExpenditureAmount += unitsAmountToWriteOff;
						unitsAmountToWriteOff = 0;
					}
				}
			}
		}
		#endregion

		#region Constants
		private static decimal TaxRate = decimal.Parse(ConfigurationManager.AppSettings["TaxRate"], CultureInfo.InvariantCulture);
		#endregion

		#region Fields
		private static Manager _instance = new Manager();
		#endregion

		#region Constructors
		private Manager()
		{
		}
		#endregion

		#region Properties
		public static Manager Instance
		{
			get
			{
				return _instance;
			}
		}
		#endregion

		#region Methods
		public List<ManagementCompany> GetManagementCompanies()
		{
			List<ManagementCompany> result;
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				result = dataContext.ManagementCompanies.ToList();
			}
			return result;
		}
		public List<UnitFund> GetUnitFunds(ManagementCompany company)
		{
			if (company == null)
			{
				throw new ArgumentNullException("company");
			}
			List<UnitFund> result;
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				result =
					(from unitFund in dataContext.UnitFunds
					 where unitFund.CompanyId == company.CompanyId
					 select unitFund).ToList();
			}
			return result;
		}
		public List<UnitFund> GetUnitFunds()
		{
			List<UnitFund> result;
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				result = dataContext.UnitFunds.ToList();
			}
			return result;
		}
		public void AddMovement(decimal unitsAmount, UnitFund sourceFund, UnitFund destinationFund, DateTime movementDate)
		{
			if (unitsAmount <= 0)
			{
				throw new ArgumentException("Units amount should be greater than zero.", "moneyAmount");
			}
			if (sourceFund.CompanyId != destinationFund.CompanyId)
			{
				throw new InvalidOperationException("Unit funds should be in the same management company.");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				UnitPrice sourceUnitPrice = GetFirstPrice(movementDate, dataContext, sourceFund.FundId);
				UnitPrice destinationUnitPrice = GetFirstPrice(movementDate, dataContext, destinationFund.FundId);
				IEnumerable<Income> activeIncomes = GetActiveIncomes(movementDate, dataContext, sourceFund.FundId);
				IEnumerator<Income> incomesEnumerator = activeIncomes.GetEnumerator();
				Operation operation = new Operation();
				operation.OperationId = Guid.NewGuid();
				operation.OperationDate = movementDate;
				operation.OperationTypeId = (int)OperationType.Exchange;
				Expenditure expenditure = new Expenditure();
				expenditure.ExpenditureId = operation.OperationId;
				expenditure.FundId = sourceFund.FundId;
				expenditure.UnitsAmount = unitsAmount;
				expenditure.MoneyAmount = unitsAmount * sourceUnitPrice.Price;
				Income income = new Income();
				income.IncomeId = operation.OperationId;
				income.FundId = destinationFund.FundId;
				WriteOffUnitsAmountForMovement(unitsAmount, sourceUnitPrice, destinationUnitPrice, incomesEnumerator, income);
				dataContext.Operations.AddObject(operation);
				dataContext.Expenditures.AddObject(expenditure);
				dataContext.Incomes.AddObject(income);
				dataContext.SaveChanges();
			}
		}
		private static void WriteOffUnitsAmountForMovement(decimal unitsAmount, UnitPrice sourceUnitPrice, UnitPrice destinationUnitPrice, IEnumerator<Income> incomesEnumerator, Income income)
		{
			while (unitsAmount != 0 && incomesEnumerator.MoveNext())
			{
				Income sourceIncome = incomesEnumerator.Current;
				decimal incomeUnitPrice = sourceIncome.MoneyAmount / sourceIncome.UnitsAmount;
				decimal unitsToWriteOff = unitsAmount;
				decimal availableUnits = Math.Min(unitsToWriteOff, sourceIncome.UnitsAmount - (sourceIncome.UnitsExpenditureAmount ?? 0));
				sourceIncome.UnitsExpenditureAmount = (sourceIncome.UnitsExpenditureAmount ?? 0) + availableUnits;
				if (availableUnits != unitsToWriteOff)
				{
					unitsAmount -= availableUnits;
				}
				else
				{
					unitsAmount = 0;
				}
				income.MoneyAmount += availableUnits * incomeUnitPrice;
				income.UnitsAmount += availableUnits * sourceUnitPrice.Price / destinationUnitPrice.Price;
			}
			if (unitsAmount != 0)
			{
				throw new InvalidOperationException("There is no enough units to create movement.");
			}
		}
		public void AddExpenditure(Expenditure expenditure, DateTime expenditureDate)
		{
			if (expenditure.UnitsAmount == 0 && expenditure.MoneyAmount == 0)
			{
				throw new InvalidOperationException("Units amount or money amount should not be equal to zero.");
			}
			if (expenditure.UnitsAmount != 0 && expenditure.MoneyAmount != 0)
			{
				throw new InvalidOperationException("Units amount or money amount should be equal to zero.");
			}
			if (expenditure.UnitsAmount < 0 || expenditure.MoneyAmount < 0)
			{
				throw new InvalidOperationException("Units amount and money amount could not be less than zero.");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				MarkdownRate markdownRate = GetLastMarkdownRate(expenditureDate, dataContext, expenditure.FundId);
				UnitPrice price = GetFirstPrice(expenditureDate, dataContext, expenditure.FundId);
				IEnumerable<Income> activeIncomes = GetActiveIncomes(expenditureDate, dataContext, expenditure.FundId);
				IEnumerator<Income> incomesEnumerator = activeIncomes.GetEnumerator();
				if (expenditure.UnitsAmount != 0)
				{
					WriteOffUnitsAmount(expenditure, markdownRate, price, incomesEnumerator);
				}
				else
				{
					WriteOffMoneyAmount(expenditure, markdownRate, price, incomesEnumerator);
				}
				expenditure.ExpenditureId = Guid.NewGuid();
				Operation operation = new Operation();
				operation.OperationId = expenditure.ExpenditureId;
				operation.OperationDate = expenditureDate;
				operation.OperationTypeId = (int)OperationType.Income;
				dataContext.Expenditures.AddObject(expenditure);
				dataContext.Operations.AddObject(operation);
				dataContext.SaveChanges();
			}
		}
		private static void WriteOffMoneyAmount(Expenditure expenditure, MarkdownRate markdownRate, UnitPrice price, IEnumerator<Income> incomesEnumerator)
		{
			decimal moneyToWriteOff = expenditure.MoneyAmount;
			while (moneyToWriteOff != 0 && incomesEnumerator.MoveNext())
			{
				Income income = incomesEnumerator.Current;
				decimal incomeUnitPrice = income.MoneyAmount / income.UnitsAmount;
				decimal expenditureUnitPrice = price.Price * (1 - markdownRate.Rate);
				decimal unitsToWriteOff;
				if (expenditureUnitPrice > incomeUnitPrice)
				{
					unitsToWriteOff = moneyToWriteOff / (price.Price + TaxRate * (incomeUnitPrice - price.Price + markdownRate.Rate * price.Price) - markdownRate.Rate * price.Price);
				}
				else
				{
					unitsToWriteOff = moneyToWriteOff / (price.Price - markdownRate.Rate * price.Price);
				}
				decimal availableUnits = Math.Min(unitsToWriteOff, income.UnitsAmount - (income.UnitsExpenditureAmount ?? 0));
				income.UnitsExpenditureAmount = (income.UnitsExpenditureAmount ?? 0) + availableUnits;
				if (availableUnits != unitsToWriteOff)
				{
					moneyToWriteOff -= CalculatePossibleExpenditure(price.Price, availableUnits, markdownRate.Rate, incomeUnitPrice);
				}
				else
				{
					moneyToWriteOff = 0;
				}
				expenditure.UnitsAmount += availableUnits;
			}
			if (moneyToWriteOff != 0)
			{
				throw new InvalidOperationException("There is no enough units to create expenditure.");
			}
		}
		private static void WriteOffUnitsAmount(Expenditure expenditure, MarkdownRate markdownRate, UnitPrice price, IEnumerator<Income> incomesEnumerator)
		{
			decimal unitsToWriteOff = expenditure.UnitsAmount;
			while (unitsToWriteOff != 0 && incomesEnumerator.MoveNext())
			{
				Income income = incomesEnumerator.Current;
				decimal incomeUnitPrice = income.MoneyAmount / income.UnitsAmount;
				decimal availableUnits = Math.Min(unitsToWriteOff, income.UnitsAmount - (income.UnitsExpenditureAmount ?? 0));
				income.UnitsExpenditureAmount = (income.UnitsExpenditureAmount ?? 0) + availableUnits;
				unitsToWriteOff -= availableUnits;
				expenditure.MoneyAmount += CalculatePossibleExpenditure(price.Price, availableUnits, markdownRate.Rate, incomeUnitPrice);
			}
			if (unitsToWriteOff != 0)
			{
				throw new InvalidOperationException("There is no enough units to create expenditure.");
			}
		}
		public static decimal CalculatePossibleExpenditure(decimal unitPrice, decimal unitsCount, decimal markdownRate, decimal incomeUnitPrice)
		{
			decimal purePossibleExpenditure = unitPrice * unitsCount;
			decimal markdown = purePossibleExpenditure * markdownRate;
			decimal profit = purePossibleExpenditure - markdown - incomeUnitPrice * unitsCount;
			decimal tax = 0;
			if (profit > 0)
			{
				tax = profit * TaxRate;
			}
			return purePossibleExpenditure - markdown - tax;
		}
		public void AddIncome(Income income, DateTime incomeDate)
		{
			if (income.UnitsAmount == 0 && income.MoneyAmount == 0)
			{
				throw new InvalidOperationException("Units amount or money amount should not be equal to zero.");
			}
			if (income.UnitsAmount != 0 && income.MoneyAmount != 0)
			{
				throw new InvalidOperationException("Units amount or money amount should be equal to zero.");
			}
			if (income.UnitsAmount < 0 || income.MoneyAmount < 0)
			{
				throw new InvalidOperationException("Units amount and money amount could not be less than zero.");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				MarkupRate markupRate = GetLastMarkupRate(incomeDate, dataContext, income.FundId);
				UnitPrice price = GetFirstPrice(incomeDate, dataContext, income.FundId);
				income.IncomeId = Guid.NewGuid();
				Operation operation = new Operation();
				operation.OperationId = income.IncomeId;
				operation.OperationDate = incomeDate;
				operation.OperationTypeId = (int)OperationType.Income;
				if (price != null)
				{
					AdjustIncomeUnitsAmount(income, markupRate, price);
				}
				else
				{
					operation.IsPendingOperation = true;
				}
				dataContext.Incomes.AddObject(income);
				dataContext.Operations.AddObject(operation);
				dataContext.SaveChanges();
			}
		}

		private static void AdjustIncomeUnitsAmount(Income income, MarkupRate markupRate, UnitPrice price)
		{
			if (income.UnitsAmount == 0)
			{
				decimal pureMoneyToBuy = income.MoneyAmount / (1 + markupRate.Rate);
				income.UnitsAmount = pureMoneyToBuy / price.Price;
			}
			else if (income.MoneyAmount == 0)
			{
				income.MoneyAmount = (income.UnitsAmount * price.Price) * (1 + markupRate.Rate);
			}
		}

		private static IEnumerable<Income> GetActiveIncomes(DateTime finalDate, UnitFundsEntities dataContext, Guid unitFundId)
		{
			return
				from income in dataContext.Incomes
				join operation in dataContext.Operations
					on income.IncomeId equals operation.OperationId
				where (income.UnitsAmount != income.UnitsExpenditureAmount || income.UnitsExpenditureAmount == null) && income.FundId == unitFundId && operation.OperationDate <= finalDate
				orderby operation.OperationDate ascending
				select income;
		}
		private static MarkdownRate GetLastMarkdownRate(DateTime finalDate, UnitFundsEntities dataContext, Guid unitFundId)
		{
			MarkdownRate result =
				(from item in dataContext.MarkdownRates
				 where item.FundId == unitFundId && item.Date < finalDate
				 orderby item.Date descending
				 select item).Take(1).Single();
			return result;
		}
		private static MarkupRate GetLastMarkupRate(DateTime finalDate, UnitFundsEntities dataContext, Guid unitFundId)
		{
			MarkupRate markupRate =
				(from item in dataContext.MarkupRates
				 where item.FundId == unitFundId && item.Date < finalDate
				 orderby item.Date descending
				 select item).Take(1).Single();
			return markupRate;
		}
		private static UnitPrice GetFirstPrice(DateTime startDate, UnitFundsEntities dataContext, Guid unitFundId)
		{
			UnitPrice price =
				(from item in dataContext.UnitPrices
				 where item.FundId == unitFundId && item.PriceDate >= startDate
				 orderby item.PriceDate ascending
				 select item).Take(1).SingleOrDefault();
			return price;
		}
		public List<FundProfitability> CalculateFundsProfitabilities(DateTime startDate, DateTime endDate)
		{
			if (endDate < startDate)
			{
				throw new ArgumentException("endDate should not be earlier than startDate", "endDate");
			}
			List<FundProfitability> result = new List<FundProfitability>();
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				ProfitabilityCalculatingHelper calculatingHelper = new ProfitabilityCalculatingHelper();
				calculatingHelper.StartDate = startDate;
				calculatingHelper.EndDate = endDate;
				calculatingHelper.FundsProfitabilites = new Dictionary<Guid, FundProfitability>();
				IEnumerable<UnitFund> unitFunds = GetUnitFunds();
				IQueryable<IGrouping<Guid, MarkdownRate>> startMarkdownRates = GetMarkdownRates(startDate, dataContext);
				IQueryable<IGrouping<Guid, UnitPrice>> startUnitPrices = GetUnitPrices(startDate, dataContext);
				calculatingHelper.StartFundsSnapshots = CreateFundSnapshots(unitFunds, startUnitPrices, startMarkdownRates);
				IQueryable<IGrouping<Guid, MarkdownRate>> endMarkdownRates = GetMarkdownRates(endDate, dataContext);
				IQueryable<IGrouping<Guid, UnitPrice>> endUnitPrices = GetUnitPrices(endDate, dataContext);
				calculatingHelper.EndFundsSnapshots = CreateFundSnapshots(unitFunds, endUnitPrices, endMarkdownRates);
				CalculateIncomes(dataContext, calculatingHelper);
				CalculateExpenditures(dataContext, calculatingHelper);
				foreach (Guid unitFund in calculatingHelper.FundsProfitabilites.Keys)
				{
					FundProfitability fundProfitability = calculatingHelper.FundsProfitabilites[unitFund];
					FundSnapshot startFundSnapshot = calculatingHelper.StartFundsSnapshots[unitFund];
					FundSnapshot endFundSnapshot = calculatingHelper.EndFundsSnapshots[unitFund];
					fundProfitability.StartValue = startFundSnapshot.PossibleMoneyExpenditure;
					fundProfitability.EndValue = endFundSnapshot.PossibleMoneyExpenditure;
					result.Add(fundProfitability);
				}
			}
			return result;
		}
		private IQueryable<IGrouping<Guid, MarkdownRate>> GetMarkdownRates(DateTime date, UnitFundsEntities dataContext)
		{
			var markdownRates =
				from markdownRate in dataContext.MarkdownRates
				where markdownRate.Date <= date
				group markdownRate by markdownRate.FundId;
			return markdownRates;
		}
		private static void CalculateIncomes(UnitFundsEntities dataContext, ProfitabilityCalculatingHelper calculatingHelper)
		{
			var incomes =
				from income in dataContext.Incomes
				join operation in dataContext.Operations
					on income.IncomeId equals operation.OperationId
				join tempExpenditure in dataContext.Expenditures
					on income.IncomeId equals tempExpenditure.ExpenditureId
					into expenditures
				from expenditure in expenditures.DefaultIfEmpty()
				where !operation.IsPendingOperation && operation.OperationDate <= calculatingHelper.EndDate
				select new
				{
					OperationType = (OperationType)operation.OperationTypeId,
					Date = operation.OperationDate,
					IncomeData = income,
					ExpenditureData = expenditure
				};
			foreach (var income in incomes)
			{
				Guid unitFundId = income.IncomeData.FundId;
				UnitFund unitFund = income.IncomeData.UnitFund;
				FundProfitability fundProfitability;
				if (!calculatingHelper.FundsProfitabilites.ContainsKey(unitFundId))
				{
					fundProfitability = CreateFundProfitability(calculatingHelper, unitFund);
					calculatingHelper.FundsProfitabilites.Add(unitFundId, fundProfitability);
				}
				else
				{
					fundProfitability = calculatingHelper.FundsProfitabilites[unitFundId];
				}
				if (income.Date > calculatingHelper.StartDate)
				{
					switch (income.OperationType)
					{
						case OperationType.Income:
							fundProfitability.OperationsAmount += income.IncomeData.MoneyAmount;
							break;
						case OperationType.Exchange:
							fundProfitability.OperationsAmount += income.ExpenditureData.MoneyAmount;
							break;
						default:
							throw new InvalidOperationException("Invalid operation type for income.");
					}
				}
				if (income.Date <= calculatingHelper.StartDate)
				{
					calculatingHelper.StartFundsSnapshots[unitFundId].UpdateWithIncomeOperation(income.IncomeData);
				}
				calculatingHelper.EndFundsSnapshots[unitFundId].UpdateWithIncomeOperation(income.IncomeData);
			}
		}

		private static FundProfitability CreateFundProfitability(ProfitabilityCalculatingHelper calculatingHelper, UnitFund unitFund)
		{
			FundProfitability fundProfitability;
			fundProfitability = new FundProfitability();
			fundProfitability.EndDate = calculatingHelper.EndDate;
			fundProfitability.FundId = unitFund.FundId;
			fundProfitability.FundName = unitFund.FundName;
			fundProfitability.StartDate = calculatingHelper.StartDate;
			return fundProfitability;
		}
		private static void CalculateExpenditures(UnitFundsEntities dataContext, ProfitabilityCalculatingHelper calculatingHelper)
		{
			var expenditures =
				from expenditure in dataContext.Expenditures
				join operation in dataContext.Operations
					on expenditure.ExpenditureId equals operation.OperationId
				where operation.OperationDate <= calculatingHelper.EndDate
				select new
				{
					Date = operation.OperationDate,
					OperationData = expenditure
				};
			foreach (var expenditure in expenditures)
			{
				Guid unitFundId = expenditure.OperationData.FundId;
				UnitFund unitFund = expenditure.OperationData.UnitFund;
				FundProfitability fundProfitability;
				if (!calculatingHelper.FundsProfitabilites.ContainsKey(unitFundId))
				{
					fundProfitability = CreateFundProfitability(calculatingHelper, unitFund);
					calculatingHelper.FundsProfitabilites.Add(unitFundId, fundProfitability);
				}
				else
				{
					fundProfitability = calculatingHelper.FundsProfitabilites[unitFundId];
				}
				if (expenditure.Date > calculatingHelper.StartDate)
				{
					fundProfitability.OperationsAmount -= expenditure.OperationData.MoneyAmount;
				}
				if (expenditure.Date <= calculatingHelper.StartDate)
				{
					calculatingHelper.StartFundsSnapshots[unitFundId].UpdateWithExpenditureOperation(expenditure.OperationData);
				}
				calculatingHelper.EndFundsSnapshots[unitFundId].UpdateWithExpenditureOperation(expenditure.OperationData);
			}
		}
		private Dictionary<Guid, FundSnapshot> CreateFundSnapshots(
			IEnumerable<UnitFund> unitFunds,
			IQueryable<IGrouping<Guid, UnitPrice>> unitPricesGroups,
			IQueryable<IGrouping<Guid, MarkdownRate>> markdownRatesGroups)
		{
			Dictionary<Guid, FundSnapshot> result = new Dictionary<Guid, FundSnapshot>();
			foreach (UnitFund unitFund in unitFunds)
			{
				FundSnapshot snapshot = new FundSnapshot();
				snapshot.UnitPrice = 0;
				snapshot.MarkdownRate = 0;
				result.Add(unitFund.FundId, snapshot);
			}
			foreach (IGrouping<Guid, UnitPrice> unitPrices in unitPricesGroups)
			{
				FundSnapshot snapshot = null;
				if (!result.ContainsKey(unitPrices.Key))
				{
					snapshot = new FundSnapshot();
					result.Add(unitPrices.Key, snapshot);
				}
				else
				{
					snapshot = result[unitPrices.Key];
				}
				snapshot.UnitPrice = unitPrices.OrderBy((unitPrice) => unitPrice.PriceDate).Last().Price;
			}
			foreach (IGrouping<Guid, MarkdownRate> markdownRates in markdownRatesGroups)
			{
				FundSnapshot snapshot = null;
				if (!result.ContainsKey(markdownRates.Key))
				{
					snapshot = new FundSnapshot();
					result.Add(markdownRates.Key, snapshot);
				}
				else
				{
					snapshot = result[markdownRates.Key];
				}
				snapshot.MarkdownRate = markdownRates.OrderBy((markdownRate) => markdownRate.Date).Last().Rate;
			}
			return result;
		}
		private static IQueryable<IGrouping<Guid, UnitPrice>> GetUnitPrices(DateTime date, UnitFundsEntities dataContext)
		{
			return
				from unitPrice in dataContext.UnitPrices
				where unitPrice.PriceDate <= date
				group unitPrice by unitPrice.FundId;
		}
		public void SetMarkupRate(MarkupRate markupRate)
		{
			if (markupRate == null)
			{
				throw new ArgumentNullException("markupRate");
			}
			if (markupRate.Rate < 0)
			{
				throw new ArgumentException("Rate should not be smaller than zero.", "markupRate");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				dataContext.MarkupRates.AddObject(markupRate);
				dataContext.SaveChanges();
			}
		}
		public void SetMarkdownRate(MarkdownRate markdownRate)
		{
			if (markdownRate == null)
			{
				throw new ArgumentNullException("markdwonRate");
			}
			if (markdownRate.Rate < 0)
			{
				throw new ArgumentException("Rate should not be smaller than zero.", "markdownRate");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				dataContext.MarkdownRates.AddObject(markdownRate);
				dataContext.SaveChanges();
			}
		}
		public void SetUnitPrice(UnitPrice unitPrice)
		{
			if (unitPrice == null)
			{
				throw new ArgumentNullException("unitPrice");
			}
			if (unitPrice.Price < 0)
			{
				throw new ArgumentException("Price should not be smaller than zero.", "unitPrice");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				dataContext.UnitPrices.AddObject(unitPrice);
				dataContext.SaveChanges();
			}
		}
		public void AddUnitFund(UnitFund unitFund)
		{
			if (unitFund == null)
			{
				throw new ArgumentNullException("unitFund");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				dataContext.UnitFunds.AddObject(unitFund);
				dataContext.SaveChanges();
			}
		}
		public void AddManagementCompany(ManagementCompany managementCompany)
		{
			if (managementCompany == null)
			{
				throw new ArgumentNullException("managementCompany");
			}
			using (UnitFundsEntities dataContext = new UnitFundsEntities())
			{
				dataContext.ManagementCompanies.AddObject(managementCompany);
				dataContext.SaveChanges();
			}
		}
		#endregion
	}
}
